
from google.appengine.ext import db
from google.appengine.ext.db import Key

from dbmodels.trait import UnitTraitModel, getUnitTraitModelKey
from dbmodels.damage import DamageModel, getDamageModelKey
from dbmodels.weapon import UnitWeaponModel, getUnitWeaponModelKey

from dbmodels.unit import UnitModel

from protorpc import messages
from protorpc import message_types
from protorpc import remote

import pprint

package = "api"

''' Used when adding a trait to the message '''
class AddUnitTraitMessage(messages.Message):
    ref = messages.StringField(1, required=True)
    rating = messages.IntegerField(2, required=False)

''' Used when getting a unit, contains all values'''
class GetUnitTraitMessage(messages.Message):
    name = messages.StringField(1, required=True)
    label = messages.StringField(2, required=True)
    type = messages.StringField(3, required=True)
    rating = messages.IntegerField(4, required=True)
    effect = messages.StringField(5, required=True)

''' Only has the references to the base weapon, used to add '''
class AddUnitWeaponMessage(messages.Message):
    ref = messages.StringField(1, required=True)
    power = messages.StringField(2, required=True)
    range = messages.IntegerField(3, required=False) # Range override, if applicable
    arc = messages.StringField(4, required=False)
    traits = messages.MessageField(AddUnitTraitMessage, 5, repeated=True) # Override traits

''' Contains all of the values, contains all values'''
class GetUnitWeaponMessage(messages.Message):
    name = messages.StringField(1, required=True)
    label = messages.StringField(2, required=True)
    power = messages.StringField(3, required=True)
    range = messages.IntegerField(4, required=False) # Range override, if applicable
    arc = messages.StringField(5, required=False)
    traits = messages.MessageField(GetUnitTraitMessage, 6, repeated=True) # Override traits

class UnitDamageMessage(messages.Message):
    structure = messages.IntegerField(1, required=False)
    crippled = messages.IntegerField(2, required=False)
    weapon = messages.IntegerField(3, required=False)
    movement = messages.IntegerField(4, required=False)
    electronic = messages.IntegerField(5, required=False)

''' Message used when a unit is added. '''
class AddUnitMessage(messages.Message):
    name = messages.StringField(1, required=True)
    label = messages.StringField(2, required=True)
    unit_type = messages.StringField(3, required=False)
    move_val = messages.IntegerField(4, required=False)
    move_type = messages.StringField(5, required=False)
    armor = messages.IntegerField(6, required=False)

    damage = messages.MessageField(UnitDamageMessage, 7, repeated=False)

    actions = messages.IntegerField(8, required=False)
    attack = messages.IntegerField(9, required=False)
    defense = messages.IntegerField(10, required=False)
    ew = messages.IntegerField(11, required=False)
    image = messages.StringField(12, required=False)

    ranged = messages.MessageField(AddUnitWeaponMessage, 13, repeated=True)
    melee = messages.MessageField(AddUnitWeaponMessage, 14, repeated=True)
    traits = messages.MessageField(AddUnitTraitMessage, 15, repeated=True)

''' Message used when a get of a unit is requested.  '''
class GetUnitMessage(messages.Message):
    name = messages.StringField(1, required=True)
    label = messages.StringField(2, required=True)
    unit_type = messages.StringField(3, required=False)
    move_val = messages.IntegerField(4, required=False)
    move_type = messages.StringField(5, required=False)
    armor = messages.IntegerField(6, required=False)

    damage = messages.MessageField(UnitDamageMessage, 7, repeated=False)

    actions = messages.IntegerField(8, required=False)
    attack = messages.IntegerField(9, required=False)
    defense = messages.IntegerField(10, required=False)
    ew = messages.IntegerField(11, required=False)
    image = messages.StringField(12, required=False)

    ranged = messages.MessageField(GetUnitWeaponMessage, 13, repeated=True)
    melee = messages.MessageField(GetUnitWeaponMessage, 14, repeated=True)
    traits = messages.MessageField(GetUnitTraitMessage, 15, repeated=True)

class GetUnits(messages.Message):
    units = messages.MessageField(GetUnitMessage, 1, repeated=True)

class GetUnitsByFactionRequest(messages.Message):
    faction = messages.StringField(1, required=True)

class UnitService(remote.Service):

    ''' Add a Unit, using references for its weapons and traits '''
    @remote.method(AddUnitMessage, message_types.VoidMessage)
    def add(self, request):
        unit = UnitModel(
            key_name = request.name,
            name = request.name,
            label = request.label,
            unit_type = request.unit_type,
            move_val = request.move_val,
            move_type = request.move_type,
            armor = request.armor,
            actions = request.actions,
            attack = request.attack,
            defense = request.defense,
            ew = request.ew,
            image = request.image
        )

        ''' Must deal with the damage type'''
        rd = request.damage
        damageKey = getDamageModelKey(rd.structure, rd.crippled, rd.weapon, rd.movement, rd.electronic)
        damageTrack = DamageModel(
            key_name = damageKey,
            structure = rd.structure,
            crippled = rd.crippled,
            weapon = rd.weapon,
            movement = rd.movement,
            electronic = rd.electronic
        )
        damageTrack.put()
        unit.damage = damageKey

        ''' For each weapon we have, try to merge '''
        self.createUnitWeaponModel(request.ranged, unit.ranged)
        self.createUnitWeaponModel(request.melee, unit.melee)

        ''' Write any traits to the model '''
        self.createUnitTraits(request.traits, unit.traits)

        ''' Write to the datastore '''
        unit.put()

        return message_types.VoidMessage()

    ''' Walks through all the weapons in the provided structure and creates models for them'''
    def createUnitWeaponModel(self, weapons, unit):
        for rwep in weapons:
            uw_k = getUnitWeaponModelKey(rwep.ref, rwep.power, rwep.range, rwep.arc, rwep.traits)
            unitWeapon = UnitWeaponModel(
                key_name = uw_k,
                ref = rwep.ref,
                power = rwep.power,
                range = rwep.range,
                arc = rwep.arc
            )

            ''' If we have any traits, merge them in '''
            if rwep.traits is not None:
                for wtrait in rwep.traits:
                    uwt_k = getUnitTraitModelKey(wtrait.ref, wtrait.rating)
                    unitWeaponTrait = UnitTraitModel(
                        key_name = uwt_k,
                        ref = wtrait.ref,
                        rating = wtrait.rating
                    )
                    unitWeaponTrait.put()
                    unitWeapon.traits.append(unitWeaponTrait.key())

            unitWeapon.put()

            unit.append(unitWeapon.key())

    ''' Walks all the traits in the response, and adds them to the unit'''
    def createUnitTraits(self, traits, unit):
        for mTrait in traits:
            ref_k = getUnitTraitModelKey(mTrait.ref, mTrait.rating)
            unitTrait = UnitTraitModel(
                key_name = ref_k,
                ref = mTrait.ref,
                rating = mTrait.rating
            )
            unitTrait.put()
            unit.append(unitTrait.key())

    ''' Get every unit associated with a faction, including all of their details '''
    @remote.method(GetUnitsByFactionRequest, GetUnits)
    def list_by_faction(self, request):

        faction_k = Key.from_path('FactionModel', request.faction)
        faction = db.get(faction_k)

        units = []
        for unit_key in faction.units:
            unit = db.get(unit_key)
            if unit is not None:
                mUnit = GetUnitMessage(
                    name = unit.name,
                    label = unit.label,
                    unit_type = unit.unit_type,
                    move_val = unit.move_val,
                    move_type = unit.move_type,
                    armor = unit.armor,
                    actions = unit.actions,
                    attack = unit.attack,
                    defense = unit.defense,
                    ew = unit.ew,
                    image = unit.image
                )

                ''' Do damage handling '''
                dam_k = Key.from_path('DamageModel', unit.damage)
                dam = db.get(dam_k)
                uDamage = UnitDamageMessage(
                    structure = dam.structure,
                    crippled = dam.crippled,
                    weapon = dam.weapon,
                    movement = dam.movement,
                    electronic = dam.electronic
                )
                mUnit.damage = uDamage

                ''' Now fetch and merge all the weapons '''
                self.mergeWeapons(unit.ranged, mUnit.ranged)
                self.mergeWeapons(unit.melee, mUnit.melee)

                ''' Now fetch and merge all the traits '''
                self.mergeTraits(unit.traits, mUnit.traits)

                units.append(mUnit)

        return GetUnits(units=units)

    ''' Merge all weapons traits into a single, unified format for output'''
    def mergeWeapons(self, weapons, unitMessage):
        ''' Fetch all the weapons and merge them with their reference values '''
        for wep_k in weapons:
            ''' rweap will be a reference, rweap.key will be the key for the value '''
            wep = db.get(wep_k)
            rwep_k = Key.from_path('WeaponModel', wep.ref)
            wepRef = db.get(rwep_k)
            # You can get None here if the trait doesn't exist
            if wepRef is None:
                continue

            wMsg = GetUnitWeaponMessage(
                name = wepRef.name,
                label = wepRef.label,
                range = wepRef.range,
                power = wep.power
            )

            # Override weapon range
            if wep.range is not None:
                wMsg.range = wep.range

            # Create an obj holding each trait reference
            tmpTraits = {}
            # Walk through the reference traits and set them up
            for trait in wepRef.traits:
                traitRef = db.get(trait)
                # Skip if we're missing the trait
                if traitRef is None:
                    continue
                tMsg = GetUnitTraitMessage(
                    name = traitRef.name,
                    label = traitRef.label,
                    type = traitRef.type,
                    effect = traitRef.effect
                )
                tmpTraits[traitRef.name.lower()] = tMsg

            # Now walk the unit traits and modify them if necessary
            for trait_k in wep.traits:
                wepTrait = db.get(trait_k)
                traitName = wepTrait.ref
                if traitName.lower() in tmpTraits:
                    tmpTraits[traitName.lower()].rating = wepTrait.rating

            for key in tmpTraits:
                wMsg.traits.append(tmpTraits[key])

            unitMessage.append(wMsg)

    def mergeTraits(self, traits, tMsg):
        '''  Fetch all the trait references and merge them '''
        for uTrait_k in traits:
            uTrait = db.get(uTrait_k)
            rTrait_k  = Key.from_path('TraitModel', uTrait.ref)
            rTrait = db.get(rTrait_k)

            # You can get none of the trait doesn't exist
            if rTrait is None:
                continue

            mTrait = GetUnitTraitMessage(
                name = rTrait.name,
                label = rTrait.label,
                type = rTrait.type,
                effect = rTrait.effect,
                rating = uTrait.rating
            )

            tMsg.append(mTrait)
